/**
 ** @author: 浓咖啡
 ** @date:  2018.5.11
 ** @brief: 服务器使用epool机制
 */

#include "server.h"
#include "msg_buf.h"

static void *start_epoll(void *arg);
static int setnonblocking(int);

#define MAX_EVENTS 500
#define FRAME_SIZE 100 //每次读取的大小

static int listenfd;
static int epfd;
struct epoll_event events[MAX_EVENTS];

/**
 * @brief 初始化服务器并开启epoll检测线程
 * @return
 */
int init_server(pthread_t *tid)
{
    int ret;
    struct sockaddr_in serveraddr;
    int addrlen = sizeof(struct sockaddr_in);
    struct epoll_event ev;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0){
        perror("listen err");
        return -1;
    }

    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = INADDR_ANY;
    serveraddr.sin_port = htons(server_port);

    if(bind(listenfd, (struct sockaddr *)&serveraddr, addrlen) < 0) {
        perror("bind err");
        return -1;
    }

    if(listen(listenfd, 20) < 0){
        perror("listen err");
        return -1;
    }

    //设置epool
    //2.6.8以后size参数被忽略，只要大于0即可。
    epfd = epoll_create(1);

    //监听listendfd
    ev.data.fd = listenfd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);

    ret = pthread_create(tid, NULL, start_epoll, NULL);
    if(ret < 0){
        perror("pthread_create err");
        return -1;
    }

    return 0;
}

/**
 * @brief epoll接收线程
 * @param arg
 * @return
 */
static void *start_epoll(void *arg)
{
    int i;
    int confd;
    struct sockaddr_in clientaddr;
    socklen_t addr_len = sizeof(clientaddr);
    struct epoll_event ev;
    char read_buf[FRAME_SIZE];
    ssize_t count;
    int nfds;

    while (1) {
        nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
        if(nfds < 0){
            err_log("epoll_wait err");
            continue;
        }

        //阻塞等待，如果退出代表有事件发生了
        for(i=0; i<nfds; i++){
            //如果有连接过来
            if(events[i].data.fd == listenfd){
                confd = accept(listenfd, (struct sockaddr *)&clientaddr, &addr_len);
                if(confd < 0){
                    err_log("accept err");
                    continue;
                }

                LOG("%s--%d connect\n", inet_ntoa(clientaddr.sin_addr),\
                    ntohs(clientaddr.sin_port));

                //首先将新来的客户端设置为非阻塞
                if(setnonblocking(confd) < 0){
                    err_log("setnonblocking err");
                    return NULL;
                }

                //把新的连接增加到epoll监听队列中
                ev.data.fd = confd;
                ev.events = EPOLLIN | EPOLLET; //设置为边沿触发
                epoll_ctl(epfd, EPOLL_CTL_ADD, confd, &ev);
            }else if(events[i].events & EPOLLIN){ //说明是客户端读数据
                int fd = -1;
                fd = events[i].data.fd;
                if(fd < 0){
                    err_log("fd err");
                    continue;
                }

                //客户端有数据，死循环读取出缓冲区中所有数据
                while (1) {
                    count = read(fd, read_buf, sizeof(read_buf));
                    if(count < 0){
                        //说明全部接收完了
                        if(errno == EAGAIN){
                            //读逻辑处理完，接着进行写操作，异步处理精髓
                            ev.data.fd = fd;
                            ev.events = EPOLLOUT | EPOLLET;
                            epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);

                            //业务逻辑

                            break;
                        }
                    }else if (count > 0) {
                        //读到的数据放到缓冲区内
                        msg_write(read_buf, count);
                    }else{//对端退出了
                        LOG("client %d quit", fd);
                        //把这个客户端从epoll监听队列中去掉
                        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
                        close(fd);
                        break;
                    }
                }
            }else if(events[i].events & EPOLLOUT){  //代表客户端要发送数据
                int fd = -1;
                fd = events[i].data.fd;

                //简单把数据写回去
                write(fd, "abcd", FRAME_SIZE);

                //重新设为读
                ev.data.fd = fd;
                ev.events = EPOLLIN | EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
            }
        }
    }
}

/**
 * @brief 将设备变为非阻塞方式
 * @param fd 要修改的文件描述符
 * @return 0 -1
 */
static int setnonblocking(int fd)
{
    int flag;
    flag = fcntl(fd, F_GETFL);
    if(flag < 0){
        err_log("F_GETFL err");
        return -1;
    }

    flag |= O_NONBLOCK;
    if(fcntl(fd, F_SETFL, flag) < 0){
        err_log("F_SETFL err");
        return -1;
    }
}
